package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.pojo.Goods;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    public Goods buildGoods(SpuDTO spuDTO) {
        //1.搜索字段，包含分类、品牌、标题等
        //1.1.查询分类
        List<CategoryDTO> categoryList = itemClient.queryCategoryByIds(spuDTO.getCategoryIds());
        String categoryName = categoryList.stream().map(CategoryDTO::getName).collect(Collectors.joining());
        //1.2.查询品牌
        BrandDTO brand = itemClient.queryBrandById(spuDTO.getBrandId());
        //1.3.拼接
        String all = spuDTO.getName() + categoryName + brand.getName();

        //2.spu下的所有sku
        //2.1.查询sku
        List<SkuDTO> skuDTOList = spuDTO.getSkus() == null ? itemClient.querySkuBySpuId(spuDTO.getId()) : spuDTO.getSkus();
        List<Map<String, Object>> skuList = new ArrayList<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String, Object> sku = new LinkedHashMap<>();
            sku.put("id", skuDTO.getId());
            sku.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            sku.put("price", skuDTO.getPrice());
            sku.put("title", skuDTO.getTitle());
            skuList.add(sku);
        }

        //3.价格,spu下的所有sku的价格
        Set<Long> price = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //4.规格参数，包含key和value
        Map<String, Object> specs = new HashMap<>();

        //4.1.规格参数的key，当前分类商品所属分类的规格参数，必须是search为true
        List<SpecParamDTO> specParams = itemClient.querySpecParams(null, spuDTO.getCid3(), true);
        //4.2.规格参数的值
        SpuDetailDTO detail =spuDTO.getSpuDetail() == null ? itemClient.querySpuDetailById(spuDTO.getId()) : spuDTO.getSpuDetail();
        //4.2.1.通用规格参数的值
        Map<Long, Object> genericSpec = JsonUtils.toMap(detail.getGenericSpec(), Long.class, Object.class);
        //4.2.2.特有规格参数的值
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(detail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });
        //4.3.封装数据
        for (SpecParamDTO param : specParams) {
            //规格参数name作为key
            String key = param.getName();
            //值，值需要去genericSpec或者specialSpec中取
            Object value = null;
            //判断是否是通用规格参数
            if (param.getGeneric()) {
                //通用
                value = genericSpec.get(param.getId());

            } else {
                //特有
                value = specialSpec.get(param.getId());
            }
            //判断是否是数值类型，如果是，则需要处理数据分段
            if (param.getNumeric()) {
                value = chooseSegment(value, param);
            }
            specs.put(key, value);
        }
        //5.创建goods
        Goods goods = new Goods();
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setId(spuDTO.getId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setAll(all);
        goods.setPrice(price);
        goods.setSkus(JsonUtils.toString(skuList));
        goods.setSpecs(specs);
        return goods;
    }

    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    public PageResult<GoodsDTO> search(SearchRequest request) {
        //0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }

        //1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //2.组织条件
        //2.1.source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        //2.2.搜索条件   Query:查询 QueryBuiler:查询的条件                  operator：all和and的关系
        queryBuilder.withQuery(builderBasicQueryBuilder(request));
        //2.3.分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        //PageRequest:Pageable的子类
        queryBuilder.withPageable(PageRequest.of(page, size));
        //3.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //4.解析结果
        //4.1.解析分页数据
        long total = result.getTotalElements();//总条数
        int totalPages = result.getTotalPages();//总页数
        List<Goods> list = result.getContent();//当前页的结果
        //4.2.转换DTO
        List<GoodsDTO> dtoList = BeanHelper.copyWithCollection(list, GoodsDTO.class);
        //5.封装并返回
        return new PageResult<>(total, totalPages, dtoList);
    }

    public Map<String, List<?>> queryFilterList(SearchRequest request) {
        Map<String, List<?>> filterList = new LinkedHashMap<>();

        //1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //2.添加过滤条件
        //2.1.搜索条件
        QueryBuilder basicQuery = builderBasicQueryBuilder(request);
        queryBuilder.withQuery(basicQuery);
        //2.2.设置size为1，减少hits的数据
        queryBuilder.withPageable(PageRequest.of(0, 1));
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        //3.添加聚合条件
        //3.1.分类的聚合
        String categoryAggName = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));
        //3.2.品牌的聚合
        String brandAggName = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        //4.查询聚合结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //5.解析聚合结果
        Aggregations aggs = result.getAggregations();
        //5.1.获取分类聚合
        List<Long> idList = handleCategoryAgg(filterList, aggs.get(categoryAggName));
        //5.2.解析品牌的聚合
        handleBrandAgg(filterList, aggs.get(brandAggName));
        //5.3.处理规格参数
        if (idList != null && idList.size() == 1) {
            handleSpecAgg(idList.get(0), basicQuery, filterList);
        }
        return filterList;
    }

    private void handleSpecAgg(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterList) {
        //1.查询分类下需要搜索过滤的规格参数名称
        List<SpecParamDTO> specParams = itemClient.querySpecParams(null, cid, true);

        //2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1.获取查询条件
        queryBuilder.withQuery(basicQuery);
        //2.2.减少查询结果(这里只需要聚合结果)
        //每页显示1个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        //3.聚合条件
        for (SpecParamDTO param : specParams) {
            //获取param的name,作为聚合名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        //4.查询并获取结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //解析每个参数的聚合结果
        Aggregations aggs = result.getAggregations();

        //5.解析聚合结果
        for (SpecParamDTO param : specParams) {
            //获取param的name,作为聚合名称
            Terms terms = aggs.get(param.getName());
            //获取聚合结果，注意，规格聚合的结果，直接是字符串，不用做特殊处理
            List<String> paramValues = terms.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsString)
                    //过滤掉空的待选项
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toList());
            //存入map
            filterList.put(param.getName(), paramValues);
        }
    }

    private QueryBuilder builderBasicQueryBuilder(SearchRequest request) {
        //1.创建布尔查询，用来封装所有条件
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //2.查询条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //3.过滤条件
        Map<String, String> filters = request.getFilters();
        for (Map.Entry<String, String> entry : filters.entrySet()) {
            //获取过滤条件的key
            String key = entry.getKey();
            //规格参数的key要做前缀specs
            if ("分类".equals(key)) {
                key = "categoryId";
            } else if ("品牌".equals(key)) {
                key = "brandId";
            } else {
                key = "specs." + key;
            }
            //value
            String value = entry.getValue();
            //添加过滤条件
            queryBuilder.filter(QueryBuilders.termQuery(key, value));
        }


        return queryBuilder;
    }

    private void handleBrandAgg(Map<String, List<?>> filterList, Terms terms) {
        //取出聚合中的桶，并处理成id的集合
        List<Long> idList = terms.getBuckets().stream()
                .map(bucket -> bucket.getKeyAsNumber()
                        .longValue()).collect(Collectors.toList());
        //根据id的集合查询分类集合
        List<BrandDTO> options = itemClient.queryBrandByIds(idList);
        //放到mao中去
        filterList.put("品牌", options);
    }

    private List<Long> handleCategoryAgg(Map<String, List<?>> filterList, Terms terms) {
        //取出聚合中的桶，并处理成id的集合
        List<Long> idList = terms.getBuckets().stream()
                .map(bucket -> bucket.getKeyAsNumber()
                        .longValue()).collect(Collectors.toList());
        //根据id的集合查询分类集合
        List<CategoryDTO> options = itemClient.queryCategoryByIds(idList);
        //放到mao中去
        filterList.put("分类", options);
        return idList;
    }

    public void saveItemIndex(Long spuId) {
        //查询spu
        SpuDTO spu = itemClient.querySpuById(spuId);
        //构建goods
        Goods goods = buildGoods(spu);
        //新增数据
        repository.save(goods);
    }

    public void deleteByID(Long spuId) {
        repository.deleteById(spuId);
    }
}
